Let the gateway pin a model to one upstream deployment - #81
Merged
Conversation
The cap was 700M tokens for 216 held-out case-runs (36 test cases x3 attempts, plus a rescore_top_k=3 pass over 36 validation cases), which is 3.24M per case-run. OfficeQA and BrowseComp-Plus both sit at ~5.05M, so Terminal-Bench was 36% tighter than its siblings for no reason recorded anywhere. Erring low is the expensive direction. An exhausted finalization budget surfaces as an upstream 429, and the held-out scoring it starves is the one thing a run exists to produce -- that is how OfficeQA lost a re-score. The per-case budget is the real spend control; these caps only need to stop a runaway. Measured after the fact, the first eight cells used at most 72.4M tokens, so 700M would in fact have sufficed and this change bought nothing on the searches run so far. Keeping it anyway: the sizing rationale holds for a full-length search, and the eight terminal-bench results already on disk were produced against this value, so committing it is what makes them reproducible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
codex reduces a model id to its last path component, so a codex cell can never
put `azure_ai/gpt-5.6-sol` on the wire. It gets the unqualified group instead,
which LiteLLM load-balances across deployments -- and the Responses API's
encrypted reasoning content is decryptable only by the deployment that produced
it. Every turn after the first fails with invalid_encrypted_content. That killed
terminal-bench `gpt-5.6-sol x codex` on r2, r3 and r4, five attempts in all.
Measured against the live proxy on 2026-08-01, replaying one encrypted reasoning
item:
gpt-5.6-sol 0 of 5 replays succeeded
azure_ai/gpt-5.6-sol 5 of 5 replays succeeded
The gateway is the only place that can supply the name the client cannot, so add
`model_aliases` to InferenceBudgetSpec and apply it in the proxy handler.
Applied AFTER the allow-list check, which is the whole design. `allowed_models`
governs what the caller may ask for, and is therefore what makes "this cell ran
gpt-5.6-sol" a true statement; the alias governs only which deployment serves
it. Rewriting first would force the aliased name into allowed_models and the
allow-list would stop describing the contestant. A test asserts that naming an
alias target directly is still refused, so an alias cannot become a second way
past the allow-list.
Unreachable keys and self-aliases are tolerated rather than rejected. One build
config serves every cell of a grid with allowed_models templated per launch, so
a map that pins the right deployment for one optimizer necessarily carries keys
the other launches never request; the first draft errored on those and would
have failed the nine non-sol terminal-bench cells. Nothing is lost by allowing
them -- an alias can only fire for a model the allow-list already admitted.
The substitution is recorded as `aliased_from` in the request log, alongside the
existing `dropped_params`, on the same principle: a change the caller cannot see
has to be auditable. refused_params is now keyed on the upstream model, since
refusing a parameter is a property of the deployment rather than of the name the
caller used, and keying read and write differently would have stopped the cache
ever hitting for an aliased model.
Leaves build.yaml byte-identical for the nine terminal-bench cells that do not need an alias, so their config is provably the one they already ran. Follows the existing variant pattern (swe-atlas-qna/baseline/build.gpt54mini.yaml). The cost is duplication: there is no include/extends mechanism for these YAMLs, so build.azure.yaml is a 183-line copy differing in one block. That is a drift hazard with no natural alarm -- divergence in a timeout or a budget would change results and fail nothing, and the azure cell would quietly stop being comparable to its nine siblings. So the guard is the point of this commit, not the file. The new test parses both documents, pops model_aliases from the variant, and asserts the remainder is equal. Verified by injecting a one-character change (max_concurrency 24 -> 25), which fails with the reason rather than a diff nobody reads. It also checks the alias reaches the gateway for optimizer_model=gpt-5.6-sol and stays inert for claude-opus-5.
Rebasing onto main brought 9a09b44, which raised terminal-bench's finalization max_tokens from 700M to 1.1B. build.azure.yaml was copied before that and still carried 700M, so the azure cell would have scored held-out against a budget 36% tighter than its nine siblings -- the starvation 9a09b44 exists to prevent. The drift guard caught it on its first live outing, which is the argument for having written it.
# Conflicts: # vero/tests/test_v05_harbor_build.py
The Codex CLI checks out git worktrees under .codex/ for its own runs -- 23MB of them in this checkout -- which showed up as untracked noise on every status. Nothing under it is ours to version. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
shehabyasser-scale
approved these changes
Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A contestant cell in the harness-engineering-bench study,
gpt-5.6-sol × codex, failedon terminal-bench and nowhere else. It ships real candidates on officeqa r1/r2/r3 and
browsecomp-plus r1/r2/r3, so it is not a routing problem with the contestant.
The fault is
invalid_encrypted_content, and it is deterministic rather thanload-dependent. An unqualified model group is load-balanced across upstream
deployments, and the Responses API's encrypted reasoning content is decryptable only by
the deployment that produced it. Replaying it against a sibling deployment fails on
every turn after the first.
Reproduced against the upstream proxy directly, with no gateway and no codex in the
path:
gpt-5.6-sol(bare group)azure_ai/gpt-5.6-sol(pinned deployment)Asking for the pinned deployment fixes it — but codex cannot express that request.
It reduces a model id to its last path component before putting it on the wire, so
azure_ai/gpt-5.6-solarrives asgpt-5.6-soland we are back where we started. Thereis no client-side fix available for this harness.
What this does
Adds
model_aliasesto an inference-gateway scope: a declared map from the name acaller may request to the upstream deployment that serves it.
The ordering is the whole design, and it is deliberate:
This runs after the allow-list check, not before.
allowed_modelscontinues togovern what the optimizer may ask for — and therefore what a cell's label means —
while
model_aliasesdecides only which deployment answers. Rewriting before thecheck would force the aliased name into
allowed_models, and the allow-list would stopdescribing the contestant.
Both names are recorded in the request log via a new
aliased_fromfield, so asubstitution the caller cannot see is still visible to whoever reads the log later.
refused_paramsis keyed onupstream_modelfor both its read and its write, sincerefusing a parameter is a property of the deployment rather than of the name used for
it. Keying the write on the requested name would never hit on the read path for an
aliased model, and the cache would re-discover the same refusal on every request.
Where it is configured
In
terminal-bench/baseline/build.azure.yaml, in the producer scope only. Theevaluation and finalization scopes are untouched — they target grok, which was never
affected.
That file is a copy of
build.yamlrather than an overlay, and it differs from it byexactly the two alias lines. Edits to
build.yamlmust be mirrored, which is nothypothetical: the first version of this change missed the finalization budget and
needed a follow-up commit to bring it back into line.
Also included, from the same investigation: terminal-bench's finalization budget is
sized like the rest of the suite.
Verification
model_dump, self-aliasesbeing dropped rather than rejected, empty targets being refused, and — the case that
matters for a shared grid config — alias keys that a given launch can never request,
since
allowed_modelsis templated per cell while the alias map is not.gpt-5.6-sol × codexterminal-bench cells have been running on this configfor 25 and 18 minutes with zero
invalid_encrypted_contentand zero 403s, wherethe old failures crashed within minutes.
Note on scope
This is a workaround at the right layer, not the root fix. The upstream remedy is
encrypted_content_affinityin the proxy'srouter_settings, exactly as the error textprescribes. This change lets the study close its last gap without waiting on that.
🤖 Generated with Claude Code
Greptile Summary
Adds per-scope model aliases to pin caller-visible model names to specific upstream deployments.
Confidence Score: 5/5
The PR appears safe to merge, with the alias preserved through compilation and applied only after the existing model allow-list check.
The changed routing path retains scope authorization, rewrites only admitted model names, forwards the effective deployment consistently, and has focused coverage for configuration propagation and runtime behavior.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR A[Caller requests model] --> B{Allowed by scope?} B -- No --> C[Return model_denied] B -- Yes --> D[Resolve model_aliases] D --> E[Apply cached refused parameters] E --> F[Send to pinned upstream deployment] F --> G[Record upstream model and aliased_from]Reviews (1): Last reviewed commit: "Ignore Codex CLI local state" | Re-trigger Greptile